#####################################################################
# Product:      KnowledgeEngine (Plone Edition)
#
# File:         Section.py
#
# Copyright:    Copyright (c) 2004, Faulkner Technologies
#
# Author:       Brian Oliver
#
# Description:  A Section is a primitive container for Component
#               within a Model.
#
#               Sections that are immediate children of Models are 
#               commonly refered to as "Page Sections" or "Pages" as
#               when rendered for an assessment editor, they are 
#               treated as web pages.
#####################################################################

#####################################################################
## Knowledge Engine Library Imports
from Products.KnowledgeEngine.Common import *
from Products.KnowledgeEngine.Core import *
from Products.KnowledgeEngine.Component import Component, ComponentInfo
from Products.KnowledgeEngine.Model.Model import Model
from Products.KnowledgeEngine.PropertyDefinition import *
from Products.KnowledgeEngine.PropertyTypes import *
from Products.KnowledgeEngine.PropertyFormats import *
from Products.KnowledgeEngine.PropertyStructures import *
from Products.KnowledgeEngine.EventDefinition import *
from Products.KnowledgeEngine.KERFProcessor import *

#####################################################################
## Zope Library Imports
from Globals import DTMLFile
from Globals import Persistent
from App.ImageFile import ImageFile
from AccessControl import ClassSecurityInfo

#####################################################################
## Python Library Imports
# (none)

#####################################################################
## SectionInfo Class Definition
class SectionInfo(ComponentInfo):
    """
        Defines the class info methods, defining information about
        the class without needing an instance of the class
    """

    #---------------------------------------------------------------#
    
    def getClassName(self):
        """
            Return the class name
        """
        
        return 'Section'

    #---------------------------------------------------------------#

    def getDisplayName(self):
        """
            Return a friendly name for the class instances
            (this is used for displaying in lists etc)
        """
    
        return "Section"
        
    #---------------------------------------------------------------#

    def getIdentityType(self):
        """
            Return the identity type.  
        """
        
        return "sec"

    #---------------------------------------------------------------#

    def isContainer(self):
        """
            Returns if the instance is providing folderish/container
            services.
            
            Override an return true if the instance is to be
            a container for other instances.
            
            NOTE: all instances support "events" so there is no
            need to return true if you require "event" support.
        """

        return true          
        
    #---------------------------------------------------------------#
    
    def getClassIcon(self):
        """
            Return the appropriate icon file for the class.
        """
        
        return ImageFile('skins/Section_icon.gif', globals())

    #---------------------------------------------------------------#

    def isChildPermitted(self, childClassInfo, childInstance = None):
        """
            Returns if the specified child is permitted as a 
            child of the class' instances.
            
            NOTE: This is from this class' instance perspective.
            
            For component containers, this always overrides what the
            return value from childClassInfo.isPermittedInContainer(...)
        """
        
        #DEFAULT RULE: Sections may have Component or Element children
        return childClassInfo.isInstanceOf('Component') or childClassInfo.isInstanceOf('Element') or childClassInfo.isInstanceOf('Event')
    
    #---------------------------------------------------------------#

    def onConfigureProperties(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register 
            class specific properties using the PropertyReflection interface.
            
            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered
            
            NOTE: This method is called before ClassInfo.onAfterStartup()            
        """

        #initialise the super-class properties for reflection
        ComponentInfo.onConfigureProperties(self)

        # 20070205 johnm    isMandatory and isUnique irrelevent on a section
        self.removePropertyDefinition('isMandatory')
        self.removePropertyDefinition('isUnique')

        #add the 'navigationTitle' property (usually displayed in the dropdown when the section is at the top-level)
        self.addPropertyDefinition(PropertyDefinition(identity="navigationTitle", displayName="Navigation Title", defaultValue=self.getDisplayName(), description="The text used to navigate to this component (like the dropdown lists in toolbars)", propertyFormat=pf_TEXT_SINGLELINE))

        #add the 'displayMode' property
        self.addPropertyDefinition(PropertyDefinition(identity="displayMode", displayName="Assessment Display Mode", description="How are sub-components/immediate child components of this section displayed when performing an assessment?", defaultValue="flat", propertyType=pt_STRING, propertyFormat=pf_NATIVE, propertyStructure=ps_ATOMIC, hasEnumeratedValues=true, enumeratedValuesMapping=[('Flat (not indented)', 'flat'), ('Indented', 'indented'), ('In Columns', 'columns'), ('Promoted (to next level)', 'promoted')]))       
         
        #self.addPropertyDefinition(PropertyDefinition(identity="reportDisplayMode", displayName="Report Display Mode", description="How are sub-components/immediate child components of this section displayed in reports?", defaultValue="flat", propertyType=pt_STRING, propertyFormat=pf_NATIVE, propertyStructure=ps_ATOMIC, hasEnumeratedValues=true, enumeratedValuesMapping=[('Flat', 'flat'), ('In Columns', 'columns')]))           
        
        #add the 'visualWidth' property
        self.addPropertyDefinition(PropertyDefinition(identity="visualWidth", displayName="Visual Width", description="The visual width of the section (when displayed in a column).  Set to zero if you require autosizing. (decimal values acceptable)", defaultValue=0.0, propertyType=pt_FLOAT, propertyFormat=pf_NATIVE, propertyStructure=ps_ATOMIC))        

        #add the 'visualMeasure' property
        self.addPropertyDefinition(PropertyDefinition(identity="visualMeasure", displayName="Visual Measurements Type", description="The type of measures used for the visual width (CSS units)", defaultValue="px", propertyType=pt_STRING, propertyFormat=pf_NATIVE, propertyStructure=ps_ATOMIC, hasEnumeratedValues=true, enumeratedValuesList=['em', 'ex', 'px', 'in', 'cm', 'mm', 'pt', 'pc']))        

        #add the 'isScored' property        
        self.addPropertyDefinition(PropertyDefinition(identity="isScored", displayName="Show Section Score in Report?", description="Should Section score be added to Scored Reports?", defaultValue=false, propertyType=pt_BOOLEAN, propertyFormat=pf_SELECTION + pf_YES_NO, propertyStructure=ps_ATOMIC))        
        
        
        self.addPropertyDefinition(PropertyDefinition(
            identity="showInPortlet", 
            displayName="Show in Navigation Portlet?", 
            description="Should this section be shown in the assessment progress portlet", 
            defaultValue=false, 
            propertyType=pt_BOOLEAN, 
            propertyFormat=pf_SELECTION + pf_YES_NO, 
            propertyStructure=ps_ATOMIC
        ))

    #---------------------------------------------------------------#

    def onConfigureEvents(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register 
            class specific events using the EventReflection interface.
            
            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered
            
            NOTE: This method is called before ClassInfo.onAfterStartup()            
        """
                
        #initialise the super-class events for reflection
        ComponentInfo.onConfigureEvents(self)

        #add the onBeforeVisiting event (which occurs prior to a component being visited)
        self.addEventDefinition(EventDefinition(identity="onBeforeVisiting", displayName="Before Visiting", description="Occurs before a component is visited", eventType=et_SERVER))

        #add the onBeforeLeaving event (which occurs prior to an assessment being closed)
        self.addEventDefinition(EventDefinition(identity="onBeforeLeaving", displayName="Before Leaving", description="Occurs before a component is left", eventType=et_SERVER))

    #---------------------------------------------------------------#

#####################################################################
## Section Class Definition
class Section(Component):
    """
        Simplest Component Container
    """

    security = ClassSecurityInfo()

    #---------------------------------------------------------------#

    #TRANSIENT: a ClassInfo implementation instance to provide info about this class
    _CLASSINFO = SectionInfo()
 
    #---------------------------------------------------------------#
    # Default Attribute Values
    isIndented = 1
   
    #---------------------------------------------------------------#

    def __init__(self, id, identity):
        "Constructor for the Section"        

        Component.__init__(self, id, identity)

    #---------------------------------------------------------------#

    def onAfterInstanciation(self):
        """
            Handle Knowledge Engine specific actions after this object 
            has been loaded into memory.  
            
            This is useful for performing backwards compatibility
        """

        #perform default onAfterInstanciation processing
        Component.onAfterInstanciation(self)

        #ensure the navigation property is set to the title (for backwards compatibility)
        if not hasattr(self, "navigationTitle"):
            if hasattr(self, "title"):
                self.navigationTitle = self.getTitleOrId()
            else:
                self.navigationTitle = ""

        #add isScored attribute if it doesn't already exist
        if not hasattr(self, "isScored"):
            self.isScored = false

        #add displayMode attribute if it doesn't already exist
        if not hasattr(self, "displayMode"):
            if hasattr(self, "isIndented"):
                self.displayMode = iif(self.isIndented, "indented", "flat")
                
            if hasattr(self, "isPromotingChildren") and self.isPromotingChildren:
                self.displayMode = 'promoted'

        #add visualWidth attribute if it doesn't already exist
        if not hasattr(self, "visualWidth"):
            self.visualWidth = 15.0

        #add visualMeasure attribute if it doesn't already exist
        if not hasattr(self, "visualMeasure"):
            self.visualMeasure = "px"
               
    #---------------------------------------------------------------#
    
    def index_html(self):
        """ Default view for a Section """
        
        return self.folder_contents(REQUEST=self.REQUEST)
    
    
    #---------------------------------------------------------------#

    def getDisplayMode(self):
        """
            Return the mode of display for the Section
        """
        
        return self.displayMode
                
                
    #---------------------------------------------------------------#
    
    def getShowInPortlet(self):
        """ Returns True if this portlet should be shown in the navigation portlet """
        
        return getattr(self.aq_explicit, 'showInPortlet', None)
    
    #---------------------------------------------------------------#
                
    def getIsShownInPageList(self, context=None):
        """
            Is this section hidden from page lists?
        """
        
        return True
                
                
    #---------------------------------------------------------------#

    def getVisualWidth(self):
        """
            Returns the visual width of the section
        """
        
        return self.visualWidth
                
    #---------------------------------------------------------------#

    def getVisualMeasure(self):
        """
            Returns the visual measurement scale of the section
        """
        
        return self.visualMeasure

    #---------------------------------------------------------------#

    def getIsScored(self):
        """
            Returns if the section should be scored
        """
        
        return self.isScored

    #---------------------------------------------------------------#

    def setIsScored(self, isScored):
        """
            Set the that the section should be scored
        """
        
        self.isScored = isScored

    #---------------------------------------------------------------#

    def getIsPromotingChildren(self):
        """
            Returns if the section should promote it's children
        """
        
        return self.displayMode == 'promoted'

    #---------------------------------------------------------------#

    def getPromotedChildren(self, assessment = None):
        """
            Return a sequence of promoted children for this Section
            (returns [] if the section isn't promoting children)
            
            This method is used to determine top-level pages.
            
            If an assessment is not provided, all possible 
            promoted children must be returned.
        """
        
        #for normal sections, we just return the children (if we are promoting)
        if self.getIsPromotingChildren():
            return self.getChildren()
        else:
            return []
        
    #---------------------------------------------------------------#

    def csvScoreDump(self, assessments):
        """
            Returns a array of data of all the scored components.
            Sub sections are also iterated over.
            
            Format:
            CID,REF,CONTENT,WEIGHTING,[DATA for each assessment...]
            
        """

        array=[]
    
        for c in self.getChildren():
            row=[]
            if c.meta_type=='Section':
                array.extend(c.csvScoreDump(assessments))
            else:
                if c.isPublishable() and c.isInstanceOf("Scorable"):
                    if c.showScore:
                        row.append(c.getIdentity())
                        row.append(c.getReference())
                        row.append('"' + c.getContent() + '"')
                        row.append(c.getWeighting())
                        for a in assessments:
                            row.append(a.getScore(c.getIdentity()))
                        array.append(row)
        return array
        
    #---------------------------------------------------------------#

    def isOrderedContainer(self):
        """
            Return if the children of the object may use ordering.
            
            This is to allow the 'Order' column in the Content
            management tab in Zope            
        """

        return true

    #---------------------------------------------------------------#

    def csvNonScoreDump(self, assessments):
        """
            Returns a array of data of all the non-scored components.
            Sub sections are also iterated over.
            
            Format:
            CID,REF,CONTENT,[DATA for each assessment...]
            
        """

        array=[]
    
        for c in self.getChildren():
            row=[]
            if c.meta_type=='Section':
                array.extend(c.csvNonScoreDump(assessments))
            else:
                if not c.meta_type=="Paragraph" and not c.isInstanceOf("Scorable"):
                    row.append(c.getIdentity())
                    row.append(c.getReference())
                    row.append('"' + c.getContentOrTitle() + '"')
                    for a in assessments:
                        if c.meta_type=='Selection':
                            index = 0
                            rc = ''
                            for d in c.getChildren():
                            
                                # the following conditional counts on Excel's ability to qualify textual fields
                                # by quoting - this allows us to handle the commas inherent
                                # in the content of multi-option selection components, such that text is imported into
                                # one cell as opposed to being forcibly comma delimited
                                
                                if a.getComponentState(d.getIdentity()).getValue() and d.getReportContentWhenSelected():
                                    rc = resolveKERF(d.getReportContentWhenSelected(), a, d)
                                    # strip out line feeds from possibly multi-line text field content
                                    if index == 0:
                                        row.append('"' + rc)
                                    else:
                                        row.append(rc)
                                    index = index + 1     
                                elif not a.getComponentState(d.getIdentity()).getValue() and d.getReportContentWhenUnselected():
                                    rc = resolveKERF(d.getReportContentWhenUnselected(), a, d)
                                    if index == 0:
                                        row.append('"' + rc)
                                    else:
                                        row.append(rc)
                                    index = index + 1
                                    
                            length = len(row)-1
                            row[length] = row[length] + '"'        
                        else:
                            row.append('"' + a.getComponentState(c.getIdentity()).getValue().replace('\r\n',',') + '"')                        
                    array.append(row)
        return array
        
    #---------------------------------------------------------------#

    def getNavigationTitle(self):
        """
            Return the navigation title for this component.
            This text is used to navigate to this component.
            eg: This text is used in the dropdown lists of the toolbars
        """
        
        return self.navigationTitle
    
    #---------------------------------------------------------------#
        
    def hasReportContent( self ):
        """ Does this component have report content? """
        
        # Check each child for report content
        for child in self.getChildren(): 
            if child.hasReportContent():
                return True
        
        # None of the Options have report content, so just check the Selection Question itself
        return Component.hasReportContent( self )        
        
#####################################################################
## Class Initialisation

# Register Component Information with the Knowledge Engine
registerGenericObjectClass(Section)

#####################################################################
